#include "Cliente.h"
#include "Excepcion.h"
#include "ProtocoloConstantes.h"
#include "MotorJuegoServicioConstantes.h"
#include "MundoServicioConstantes.h"
#include "PartidaServicioConstantes.h"
#include "ServicioConstantes.h"

using namespace std;
using namespace TP;
using namespace TP::Comunicacion;

Cliente::Cliente(Jugador* pJugador, Conexion* pConexion, std::ostream& pLog) :
		_log(pLog) {
	this->_jugador = pJugador;
	this->_conexion = pConexion;
	this->_partida = NULL;
}

Cliente::Cliente(Jugador* pJugador, const string& pIp, const string& pPuerto,
		std::ostream& pOstream) : _log(pOstream) {
	this->_jugador = pJugador;
	this->_conexion = new Conexion(pIp, pPuerto);
	this->_partida = NULL;
}

Cliente::~Cliente() {
	delete this->_conexion;
}

void Cliente::Iniciar() throw (Excepcion&) {
	try {
		this->_conexion->Iniciar();
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("No se puede inicar el cliente.");
		throw;
	}
}

void Cliente::Finalizar() throw (Excepcion&) {
	try {
		this->_conexion->Finalizar();
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("No se puede finalizar el cliente.");
		throw;
	}
}

void Cliente::ejecutarComando(const string& pComando) throw (Excepcion&) {
	try {
		this->enviarMensaje(pComando);
		string respuesta = "";
		this->esperarRespuesta(respuesta);
		this->_log << respuesta;
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("No se puede ejecutar el comando.");
		throw;
	}
}

void Cliente::ejecutarComando(const string& pComando,
		vector <string>& pRespuesta) throw (Excepcion&) {
	try {
		this->enviarMensaje(pComando);
		string respuesta = "";
		this->esperarRespuesta(respuesta);
		if (respuesta.length() == 0)
			throw Excepcion("Formato de respuesta de servicio invalido.");

		pRespuesta.clear();
		std::stringstream stream(respuesta);
		string item = "";
		while (std::getline(stream, item, SEPARADOR_CAMPO))
			pRespuesta.push_back(item);
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("Fallo al ejecutar el comando.");
		throw;
	}
}

void Cliente::enviarMensaje(const string& pMensaje) throw (Excepcion&) {
	try {
		this->_conexion->Escribir(pMensaje);
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("Servicio no disponible.");
		throw;
	}
}

void Cliente::esperarRespuesta(string& pMensaje) throw (Excepcion&) {
	try {
		pMensaje = "";
		this->_conexion->Leer(pMensaje);
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("Fallo al esperar el resultado de la ejecucion.");
		throw;
	}
}

void Cliente::atacar(Posicion *pPosicion) throw (Excepcion&) {
	try {
		if (!this->_partida)
			throw Excepcion("El jugador no se encuentra jugando partida"
					" alguna.");

		_mutex.lock();

		// 0: MOTOR_JUEGO, 1: atacar, 2: jugador, 3: nombrepartida, 4: posicion;

		string comando = MOTOR_JUEGO;
		comando += SEPARADOR_CAMPO;
		comando += ATACAR;
		comando += SEPARADOR_CAMPO;
		comando += this->_jugador->aXML();
		comando += SEPARADOR_CAMPO;
		comando += this->_partida->getNombre();
		comando += SEPARADOR_CAMPO;
		comando += pPosicion->aXML();
		comando += SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando, respuesta);

		if (_OK == respuesta[0]) {
			this->_log << respuesta[1] << endl;
		}
		else if (_ERROR == respuesta[0])
			this->_log << respuesta[1] << endl;
		else
			throw Excepcion("Formato de respuesta de servicio invalido.");

		_mutex.unlock();
	}
	catch (Excepcion& pEx) {
		_mutex.unlock();
		pEx.expandirMensaje("Fallo al ejecutar el comando atacar.");
		throw;
	}
}

Partida* Cliente::crearPartida(const string& pNombreMundo,unsigned int pIdNivel,
		const string& pNombrePartida) throw (Excepcion&) {
	try {
		_mutex.lock();

		// formato de comando:
		// Partida.CrearPartida (Jugador, nombreMundo, idNivel, pNombrePartida)
		std::stringstream comando;

		comando << PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << CREAR_PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << this->_jugador->aXML();
		comando << SEPARADOR_CAMPO;
		comando << pNombreMundo;
		comando << SEPARADOR_CAMPO;
		comando << pIdNivel;
		comando << SEPARADOR_CAMPO;
		comando << pNombrePartida;
		comando << SEPARADOR_CAMPO;

		vector<string> respuesta;

		this->ejecutarComando(comando.str(), respuesta);
		this->_partida = NULL;
		if (_OK == respuesta[0]) {
			this->_partida = Partida::crearDeXML(respuesta[1]);
			if (NULL == this->_partida) {
				this->_log << respuesta[1] << endl;
				throw Excepcion("No se puede rehidratar la Partida.");
			}
		}
		else if (_ERROR == respuesta[0]) {
			this->_log << respuesta[1] << endl;
			throw Excepcion(respuesta[1]);
		}
		else {
			this->_log << respuesta[1] << endl;
			throw Excepcion("Respuesta de servicio invalida.");
		}
		_mutex.unlock();
		return this->_partida;
	}
	catch (Excepcion& pEx) {
		_mutex.unlock();
		pEx.expandirMensaje("No se puede crear la partida.");
		throw;
	}
}

Partida* Cliente::getPartida() throw (Excepcion&) {
	try {
		_mutex.lock();

		// formato de comando: MotorJuego.GetPartida (Jugador)
		string comando = MOTOR_JUEGO;
		comando += SEPARADOR_CAMPO;
		comando += GET_PARTIDA;
		comando += SEPARADOR_CAMPO;
		comando += this->_jugador->aXML();
		comando += SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando, respuesta);
		if (_OK == respuesta[0]) {
			if (NULO != respuesta[1])
				this->_partida = Partida::crearDeXML(respuesta[1]);
			else
				this->_partida = NULL;
		}
		else if (_ERROR == respuesta[0])
			this->_log << respuesta[1] << endl;
		else
			throw Excepcion("Respuesta del serivicio invalida.");

		_mutex.unlock();
		return this->_partida;
	}
	catch (Excepcion& pEx) {
		_mutex.unlock();
		pEx.expandirMensaje("Fallo al solicitar la partida al servidor.");
		throw;
	}
}

Partida* Cliente::unirAPartida(const string& pNombrePartida) throw (Excepcion&) {
	// formato de comando: Partida.ingresarAPartida (Jugador, pNombrePartida)
	try {
		_mutex.lock();

		std::stringstream comando;
		comando << PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << INGRESAR_A_PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << this->_jugador->aXML();
		comando << SEPARADOR_CAMPO;
		comando << pNombrePartida;
		comando << SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando.str(), respuesta);
		this->_partida = NULL;
		if (_OK == respuesta[0]) {
			this->_partida = Partida::crearDeXML(respuesta[1]);
			if (NULL == this->_partida) {
				this->_log << respuesta[1] << endl;
				throw Excepcion("No se puede rehidratar la Partida.");
			}
		}
		else if (_ERROR == respuesta[0]) {
			this->_log << respuesta[1] << endl;
			throw Excepcion(respuesta[1]);
		}
		else {
			this->_log << respuesta[1] << endl;
			throw Excepcion("Respuesta de servicio invalida.");
		}
		_mutex.unlock();
		return this->_partida;
	}
	catch (Excepcion& pEx) {
		_mutex.unlock();
		pEx.expandirMensaje("No se puede jugar la partida.");
		throw;
	}
}

vector<string> Cliente::listarPartidas() throw (Excepcion&) {
	try {
		// Limpio la lista de partidas.
		this->_listaPartidas.clear();

		// formato de comando: Partida.listarPartidas ()
		string comando = PARTIDA;
		comando += SEPARADOR_CAMPO;
		comando += LISTAR_PARTIDAS;
		comando += SEPARADOR_CAMPO;

		vector <string> respuesta;
		this->ejecutarComando(comando, respuesta);

		if (_OK == respuesta[0]) {
			if (NULO != respuesta[1])
				this->parsearLista(this->_listaPartidas, respuesta[1]);
		}
		else if (_ERROR == respuesta[0])
			this->_log << respuesta[1] << endl;
		else
			throw Excepcion("Respuesta de servicio invalida.");

		return this->_listaPartidas;
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("Fallo al obtener la lista de partidas.");
		throw;
	}
}

vector<string> Cliente::listarMundos() throw (Excepcion&) {
	try {
		// Inicializo la lista de mundos. 
		this->_listaMundos.clear();

		// formato de comando: 
		// Partida.CrearPartida (nombreJugador, nombreMundo, nombreNivel)
		string comando = MUNDO;
		comando += SEPARADOR_CAMPO;
		comando += LISTAR_MUNDOS;
		comando += SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando, respuesta);

		if (_OK == respuesta[0])
			this->parsearLista(this->_listaMundos, respuesta[1]);
		
		else if (_ERROR == respuesta [0])
			this->_log << respuesta[1] << endl;

		else
			throw Excepcion("Formato de respuesta de servicio invalido.");

		return this->_listaMundos;
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("Fallo al listar mundos.");
		throw;
	}
}

vector<string> Cliente::listarNiveles(const string &pNombreMundo)
		throw (Excepcion&) {
	try {
		// Inicializo la lista de niveles.    
		this->_listaNiveles.clear();

		// formato de comando: Mundo.ListarNiveles(nombreMundo)
		string comando = MUNDO;
		comando += SEPARADOR_CAMPO;
		comando += LISTAR_NIVELES;
		comando += SEPARADOR_CAMPO;
		comando += pNombreMundo;
		comando += SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando, respuesta);
		if (_OK == respuesta[0]) {
			if (NULO != respuesta[1])
				this->parsearLista(this->_listaNiveles, respuesta[1]);
		}
		else if (_ERROR == respuesta[0])
			this->_log << respuesta[1] << endl;

		else
			throw Excepcion("Formato de respuesta de servicio invalido.");

		return this->_listaNiveles;
	}
	catch (Excepcion& pEx) {
		pEx.expandirMensaje("No se puede obtener la lista de niveles.");
		throw;
	}
}

void Cliente::parsearLista(vector<string> &partidas,
		const string & pDescriptor) {
	std::stringstream ssParametros(pDescriptor);
	partidas.clear();
	string item = "";
	while (std::getline(ssParametros, item, SEPARADOR_CAMPO_LISTA))
		if (item != "")
			partidas.push_back(item);
}

void Cliente::abandonarPartida() throw (Excepcion&) {
	try {
		_mutex.lock();

		// formato de comando: Partida.abandonarPartida (Jugador)
		std::stringstream comando;
		comando << PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << ABANDONAR_PARTIDA;
		comando << SEPARADOR_CAMPO;
		comando << this->_jugador->aXML();
		comando << SEPARADOR_CAMPO;

		vector<string> respuesta;
		this->ejecutarComando(comando.str(), respuesta);
		this->_partida = NULL;
		if (_OK == respuesta[0]) {
			this->_log << respuesta[1] << endl;
		}
		else if (_ERROR == respuesta[0]) {
			this->_log << respuesta[1] << endl;
			throw Excepcion(respuesta[1]);
		}
		else {
			this->_log << respuesta[1] << endl;
			throw Excepcion("Respuesta de servicio invalida.");
		}
		_mutex.unlock();
	}
	catch (Excepcion& pEx) {
		_mutex.unlock();
		pEx.expandirMensaje("No se puede abandonar la partida.");
		throw;
	}
}